#include "io.h"

#include <iostream>
#include <sstream>
#include <fstream>

#include <algorithm>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/param.h>

#include <dirent.h>
#include <string.h>
#include "FeatureFunctions.h"
#include "tinyxml/tinyxml.h"
#include "Config.h"


/*                            *
 *    wordfile xml methods    *
 *                            */

vector<WordsLine> readXml(string filename)
{
   
  
  //function declarations
  WordsLine ElementToWordsLine(TiXmlElement* lineElement, string documentName);
  Word ElementToWord(TiXmlElement* wordElement, int lineNr, string documentName);

  //read file
  vector<WordsLine> vec;
  TiXmlDocument document( filename.c_str() );
  
  if( !document.LoadFile() )
  {
    cerr << "Error loading File " << filename << endl;
    return vec;
  }
  
  TiXmlHandle docHandle( &document );
  TiXmlNode* ImageNode = docHandle.FirstChild( "Image" ).ToNode();
  if ( ImageNode ) 
  {
    string documentName = ImageNode->ToElement()->Attribute("name") ? ImageNode->ToElement()->Attribute("name") : "";
    //add lines
    for( TiXmlNode* lineNode = ImageNode->FirstChild(); lineNode; lineNode = lineNode->NextSibling() )
    {
      WordsLine line = ElementToWordsLine(lineNode->ToElement(), documentName);
      
      for( TiXmlNode* wordNode = lineNode->FirstChild(); wordNode; wordNode = wordNode->NextSibling() )
        line.addWord(ElementToWord(wordNode->ToElement(), line.lineNumber, documentName));
      
      vec.push_back(line);
    }
  }
  
  return vec;
}

void writeXml(vector<WordsLine> lines, string fileName)
{
  //function declarations
  TiXmlElement* WordsLineToElement(WordsLine line, int no);
  TiXmlElement* WordToElement(Word word, int no);
  
  //create document
  TiXmlDocument doc;
  TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "UTF-8", "" );
  doc.LinkEndChild( decl );

  TiXmlElement* imageTag = new TiXmlElement( "Image" );
  imageTag->SetAttribute("name", fileName.c_str());
  doc.LinkEndChild( imageTag );
  for(size_t lineNr = 0; lineNr < lines.size(); lineNr++)
  {
    TiXmlElement* lineTag = WordsLineToElement(lines[lineNr], lineNr + 1);
    imageTag->LinkEndChild(lineTag);
    
    for(size_t wordNr = 0; wordNr < lines[lineNr].words.size(); wordNr++)
    {
      TiXmlElement* wordTag = WordToElement(lines[lineNr].words[wordNr], lineNr + 1);
      lineTag->LinkEndChild(wordTag);
    }
  }
  doc.SaveFile( fileName.c_str() );
}




/*                   *
 * database methods  *
 *                   */

vector< pair<string, Descriptors> > readDatabase(string filename)
{
  //read file
  ifstream databasefile(filename.c_str());
  vector<pair<string, Descriptors> > prototypes;
  

  string line;
  while (! databasefile.eof() )
  {
    getline (databasefile, line);
    pair<string, Descriptors > prototype = lineToPrototype(line);
    if(0 < prototype.second.radonDescriptor.size() &&
      Config::getInstance()->bottomProfileSampleNumber ==  prototype.second.bottomProfileDescriptor.size() &&
      (Config::getInstance()->lowResolutionWordWidth * Config::getInstance()->lowResolutionWordHeight ) ==  prototype.second.lowResolution.size()) {
      prototypes.push_back(prototype);
    }else
    {
      cerr << "ERROR: something went wrong during reading of database." << endl;
      //cerr << "       error in line:" << line << endl;
    }
    
  }
  cout << "nrPrototypes loaded from Database = " << prototypes.size() << endl;
  databasefile.close();
  prototypes.pop_back(); 
  return prototypes;
}

void writeDatabase(vector< pair<string, Descriptors > > prototypes, string filename)
{
  ofstream databasefile(filename.c_str());
  for(size_t index = 0; index < prototypes.size(); index++)
  {
    databasefile << prototypeToLine(prototypes.at(index)) << endl;
  }
  databasefile.close();

}

void writeAllToCSV(vector< pair<string, Descriptors > > prototypes){
  writeToCSV(prototypes, "wordLength.csv", "wordLength");
  writeToCSV(prototypes, "heightWidthRatio.csv", "heightWidthRatio");
  writeToCSV(prototypes, "radon.csv", "radon");
  writeToCSV(prototypes, "bottomProfile.csv", "bottomProfile");
  writeToCSV(prototypes, "lowResolution.csv", "lowResolution");
}

void writeToCSV(vector< pair<string, Descriptors > > prototypes, string fileName, string type){
  bool first = true;
  //string seperationValue = endl;
  ofstream file(fileName.c_str());
  vector<double> values = getValues(prototypes, type);
  for(size_t index = 0; index < values.size(); index++){
      if(first)first = false;
      else file << endl;
      file << values[index];
  }
  file.close();
}

double getMinimum(vector<double> values){
  double minimum = values[0];
  for(size_t index = 0; index < values.size(); index++){
    if(minimum < values[index]) continue;
    minimum = values[index];
  }
  return minimum;
}

double getMaximum(vector<double> values){
  double maximum = values[0];
  for(size_t index = 0; index < values.size(); index++){
    if(values[index] < maximum) continue;
    maximum = values[index];
  }
  return maximum;
}

vector<pair<double, double> > GetHistogram(vector<double> values, unsigned steps){
  double minimum = getMinimum(values);
  double maximum = getMaximum(values);
  if(maximum == 270) maximum = 18; //heightwidth ratio
  double delta = maximum - minimum;
  double stepSize = delta/double(steps);
 // cout << endl << "Minimum: " << minimum << " Maximum: " << maximum << " Delta: " << delta << " Step size: " << stepSize << endl;
  vector<pair<double, double> > histogram;
  for(unsigned i = 0; i < steps; i++){
    histogram.push_back(pair<double, double>(minimum + i * stepSize ,0));
  }
  
  for(unsigned i = 0; i < values.size(); i++){
    double value = values[i];    
    unsigned histogramIndex = floor((value - minimum)/stepSize);
    if(histogramIndex == steps)histogramIndex = steps-1;
    if(values.size() <= histogramIndex)continue;
    histogram[histogramIndex].second += 1;
  }
  
  return histogram;
}

void writeAllHistogramsToCSV(vector< pair<string, Descriptors > > prototypes, int steps){
  writeToHistogramToCSV(prototypes, "histogramWordLength.csv", steps, "wordLength");
  writeToHistogramToCSV(prototypes, "histogramHeightWidthRatio.csv", steps, "heightWidthRatio");
  writeToHistogramToCSV(prototypes, "histogramRadon.csv", steps, "radon");
  writeToHistogramToCSV(prototypes, "histogramBottomProfile.csv", steps, "bottomProfile");
  writeToHistogramToCSV(prototypes, "histogramLowResolution.csv", steps, "lowResolution");
}

void writeToHistogramToCSV(vector< pair<string, Descriptors > > prototypes, string fileName, int steps, string type){
  bool first = true;
  //string seperationValue = endl;
  ofstream file(fileName.c_str());
  vector<double> values = getValues(prototypes, type);
  vector<pair<double, double> > histogram = GetHistogram(values, steps);
  for(size_t index = 0; index < histogram.size(); index++){
      if(first)first = false;
      else file << endl;
      file << histogram[index].first << "," << histogram[index].second;
  }
  file.close();
}

vector<double> getValues(vector< pair<string, Descriptors > > prototypes, string type){
  vector<double> values;
  for(size_t index = 0; index < prototypes.size(); index++){
    Descriptors prototype = prototypes[index].second;
    if(type == "wordLength"){
      values.push_back(prototype.wordLength);
    }
    else if(type == "heightWidthRatio"){    
      values.push_back(prototype.heightWidthRatio);
    }
    else if (type == "radon"){  
      for(unsigned index = 0; index < prototype.radonDescriptor.size(); index++){
        values.push_back( prototype.radonDescriptor.at(index));
      }
    }else if (type == "bottomProfile"){
      for(unsigned index = 0; index < prototype.bottomProfileDescriptor.size(); index++){
	values.push_back(prototype.bottomProfileDescriptor.at(index));
      }
    }else if (type == "lowResolution"){
      for(unsigned index = 0; index < prototype.lowResolution.size(); index++){
        values.push_back(prototype.lowResolution.at(index));
      }
    }
  }
  return values;
}

pair<string, Descriptors > lineToPrototype(string line)
{
  pair<string, Descriptors > prototype;
  stringstream ss(line);
  unsigned radonDescriptorCount = 0;
  unsigned bottomProfileDescriptorCount = 0;
  unsigned lowResolutionCount = 0;
  double radonFeature;
  int bottomProfileFeature;
  int lowResolutionFeature;

  //ss << line;   
  ss >> prototype.first;
  ss >> prototype.second.wordLength;
  ss >> prototype.second.heightWidthRatio;
  ss >> radonDescriptorCount;
  for(unsigned index = 0; index < radonDescriptorCount; index++) {
    ss >> radonFeature;
    prototype.second.radonDescriptor.push_back(radonFeature);
  }
  ss >> bottomProfileDescriptorCount;
  for(unsigned index = 0; index < bottomProfileDescriptorCount; index++) {
    ss >> bottomProfileFeature;
    prototype.second.bottomProfileDescriptor.push_back(bottomProfileFeature);
  } 
  ss >> lowResolutionCount;
  for(unsigned index = 0; index < lowResolutionCount; index++) {
    ss >> lowResolutionFeature;
    prototype.second.lowResolution.push_back(lowResolutionFeature);
  } 
  return prototype;
}

string prototypeToLine(pair<string, Descriptors > prototype)
{
  stringstream ss;
  ss << prototype.first;
  ss << " " << prototype.second.wordLength;
  ss << " " << prototype.second.heightWidthRatio;
  ss << " " << prototype.second.radonDescriptor.size();
  for(unsigned index = 0; index < prototype.second.radonDescriptor.size(); index++)
  {
    ss << " " << prototype.second.radonDescriptor.at(index);
  }
  ss << " " << prototype.second.bottomProfileDescriptor.size();
  for(unsigned index = 0; index < prototype.second.bottomProfileDescriptor.size(); index++)
  {
    ss << " " << prototype.second.bottomProfileDescriptor.at(index);
  }
  ss << " " << prototype.second.lowResolution.size();
  for(unsigned index = 0; index < prototype.second.lowResolution.size(); index++)
  {
    ss << " " << prototype.second.lowResolution.at(index);
  }
  return ss.str();
  
}











void SaveLineImage(string folderName, WordsLine& line, PamImage& image){    
    stringstream imageFileName;
    imageFileName << "Line" << line.lineNumber << ".ppm";
    SaveImage(folderName + "/" + line.documentName, imageFileName.str(), image);
}

void SaveWordImage(string folderName, Word& word, PamImage& image){  
  
    if(word.text.find("/") != string::npos)return;
    
    stringstream fileName;   	
    fileName << "Word" << word.wordNumber << "_" << word.text << "_(" << word.left << ", " << word.right << ")_" << word.score << ".ppm";
    
    stringstream mapName;
    mapName << folderName << "/" << word.documentName << "/Line" << word.lineNumber;
    
    SaveImage(mapName.str(), fileName.str(), image);
}

void SaveImage(string folderName, string fileName, PamImage& image){
  //creating folders
  mkdir("outputImages",0777);
  
  string folder = "outputImages/";
  
  while(folderName.find("/") != string::npos){    
    folder += "/" + folderName.substr(0, folderName.find("/"));
    mkdir(folder.c_str(),0777);
    
    folderName = folderName.substr(folderName.find("/") + 1, folderName.length());
  }
  
  folder += "/" + folderName;
  mkdir(folder.c_str(),0777);
  
  //saving image
  string file = folder + "/" + fileName;
  image.save(file);  
}












/*                             *
 * element <-> word conversion *
 *                             */

WordsLine ElementToWordsLine(TiXmlElement* lineElement, string documentName)
{
  int top = atoi(lineElement->Attribute("top"));
  int bottom = atoi(lineElement->Attribute("bottom"));
  int left = atoi(lineElement->Attribute("left"));
  int right = atoi(lineElement->Attribute("right"));
  int shear = atoi(lineElement->Attribute("shear"));
  int no = atoi(lineElement->Attribute("no"));
  WordsLine wl(top,bottom,left,right,shear, no, documentName);
  return wl;
}

Word ElementToWord(TiXmlElement* wordElement, int lineNr, string documentName)
{
  int top = atoi(wordElement->Attribute("top"));
  int bottom = atoi(wordElement->Attribute("bottom"));
  int left = atoi(wordElement->Attribute("left"));
  int right = atoi(wordElement->Attribute("right"));
  int shear = atoi(wordElement->Attribute("shear"));
  int no = atoi(wordElement->Attribute("no"));
  string text = wordElement->Attribute("text") ? wordElement->Attribute("text") : "";
  Word w(top,bottom,left,right,shear,text, lineNr, no, documentName);
  return w;
}

TiXmlElement* WordsLineToElement(WordsLine line, int no)
{
  TiXmlElement* lineTag = new TiXmlElement( "TextLine" );
  lineTag->SetAttribute("no", no);
  lineTag->SetAttribute("top", line.top);
  lineTag->SetAttribute("bottom", line.bottom);
  lineTag->SetAttribute("left", line.left);
  lineTag->SetAttribute("right", line.right);
  lineTag->SetAttribute("shear", line.shear);
  return lineTag;
}

TiXmlElement* WordToElement(Word word, int no)
{
  TiXmlElement* wordTag = new TiXmlElement( "Word" );
  wordTag->SetAttribute("no", no);
  wordTag->SetAttribute("top", word.top);
  wordTag->SetAttribute("bottom", word.bottom);
  wordTag->SetAttribute("left", word.left);
  wordTag->SetAttribute("right", word.right);
  wordTag->SetAttribute("shear", word.shear);
  wordTag->SetAttribute("text", word.text.c_str());
  return wordTag;
}




/*                             *
 * element <-> prototype       *


pair<string, Descriptor> ElementToPrototype(TiXmlElement* prototypeElement)
{
  pair<string, Descriptor> prototype = pair<string, Descriptor>();
  string text      = prototypeElement->Attribute("text");
  int featurecount = atoi(prototypeElement->Attribute("featurecount"));

  for(int index = 0; index < featurecount; index++ )
  {

    std::stringstream ss;
    ss << index;   
    string attribute = "f" + ss.str();
    prototype.second.push_back(atoi(prototypeElement->Attribute(attribute.c_str())));
  }
  return prototype;
}

TiXmlElement* PrototypeToElement(pair<string, Descriptor > prototype)
{
  TiXmlElement* prototypeTag = new TiXmlElement( "Prototype");
  prototypeTag->SetAttribute("text", prototype.first.c_str());
  prototypeTag->SetAttribute("featurecount", prototype.second.size());

  for(unsigned index = 0; index < prototype.second.size(); index++ )
  {
    std::stringstream ss1;
    ss1 << index;   
    string attribute = "f" + ss1.str();
    

    std::stringstream ss2;
    ss2 << prototype.second.at(index);   

    prototypeTag->SetAttribute(attribute.c_str(), ss2.str().c_str());
  }
  return prototypeTag;
}

 *                             */









/*                   *
 * directory methods *
 *                   */

vector< string > readDirectory(string pathname, int length)
{
  //fix trailing '/'
  if(pathname[pathname.length() - 1] == '/')
    pathname = pathname.erase(pathname.length() - 1);
  
  vector< string > allFiles;
  
  //try to open directory
  DIR *dp;
  struct dirent *ep;
  dp = opendir (pathname.c_str());
  if (dp != NULL)
  {
    cout << "reading directory: " << pathname << ": ";
    
    while ( (ep = readdir (dp)) )
    {
      if(ep->d_name[0] != '.') // && strcmp(ep->d_name, "..") && strcmp(ep->d_name, ".svn"))
      {
	string filename = length == LONG ? pathname + "/" : "";
	filename += ep->d_name;
	
	allFiles.push_back(filename);
	
	//cout << "file " << filename << " included." << endl;
      }
    }
    
    (void) closedir (dp);
  }
  else
    cerr << "Error opening directory " << pathname << endl;
  
  //make sure files have the same order
  sort (allFiles.begin(), allFiles.end());
  
  cout << allFiles.size() << " files found." << endl;
  
  return allFiles;
}





